package com.chengqs.summer.utils;

import com.chengqs.summer.annotation.Bean;
import com.chengqs.summer.annotation.Component;
import com.chengqs.summer.exception.BeanDefinitionException;
import jakarta.annotation.Nullable;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 这个类提供了一些静态方法，用于处理 Java 类及其注解的常见操作。
 *
 * 设计模式：
 *
 * <ul>
 * <li><b>工具类模式（Utility Class Pattern）</b>: 这个类是一个典型的工具类，所有方法都是静态的，
 * 允许在不创建实例的情况下直接调用这些方法。</li>
 * <li><b>单例模式（Singleton Pattern）</b>: 虽然没有显式实现单例模式，但作为工具类，它隐含了单例模式的思想，
 * 保证了在整个应用中使用的 `ClassUtils` 类实例的一致性。</li>
 * <li><b>策略模式（Strategy Pattern）</b>: 在一些方法中（如 `findAnnotation` 和 `getBeanName`），
 * 使用了策略模式的思想，通过不同的策略来处理注解的查找和 Bean 名称的获取。</li>
 * <li><b>异常处理模式（Exception Handling Pattern）</b>: 在执行注解查找和方法操作时，使用了自定义异常
 * `BeanDefinitionException` 来处理异常情况，提供明确的错误信息。</li>
 * <li><b>反射模式（Reflection Pattern）</b>: 多个方法（如 `findAnnotation`, `getBeanName`, `findAnnotationMethod`
 * 和 `getNamedMethod`）依赖 Java 的反射机制来动态地访问类的信息和操作注解。</li>
 * </ul>
 */
public class ClassUtils {

    /**
     * 递归查找指定类或其注解上的注解。
     * @param target 目标类，表示需要在该类及其注解中查找指定注解的类。
     * @param annoClass 注解类型，表示需要查找的注解类型。
     * @return 返回目标类上或目标类的注解中找到的指定注解。如果找到多个相同的注解，则抛出 BeanDefinitionException 异常。
     * @param <A> 注解类型
     */
    public static <A extends Annotation> A findAnnotation(Class<?> target, Class<A> annoClass) {
        // 尝试从目标类上直接获取指定的注解。
        A a = target.getAnnotation(annoClass);
        // 递归检查目标类的所有注解
        for (Annotation anno : target.getAnnotations()) {
            Class<? extends Annotation> annoType = anno.annotationType();
            if (!annoType.getPackageName().equals("java.lang.annotation")) {
                // 递归检查目标类的所有注解
                A found = findAnnotation(annoType, annoClass);
                if (found != null) {
                    if (a != null) {
                        // 如果找到多个相同类型的注解，抛出 BeanDefinitionException 异常。
                        throw new BeanDefinitionException("Duplicate @" + annoClass.getSimpleName() + " found on class " + target.getSimpleName());
                    }
                    a = found;
                }
            }
        }
        return a;
    }

    /**
     * 从注解数组中获取指定的注解。
     * @param annos 注解数组。
     * @param annoClass 需要获取的注解类型。
     * @return 如果注解数组中存在指定类型的注解，则返回该注解；否则返回 null。
     * @param <A>
     */
    @Nullable
    @SuppressWarnings("unchecked")
    public static <A extends Annotation> A getAnnotation(Annotation[] annos, Class<A> annoClass) {
        // 遍历注解数组，检查每个注解是否为指定类型的注解。
        for (Annotation anno : annos) {
            if (annoClass.isInstance(anno)) {
                return (A) anno;
            }
        }
        return null;
    }

    /**
     * 从方法上获取 Bean 的名称。用于处理标注了 @Bean 注解的方法。
     * @param method 标注了 @Bean 注解的方法。
     * @return 方法的 Bean 名称。如果 @Bean 注解的 value 属性为空，则使用方法名作为 Bean 名称。
     */
    public static String getBeanName(Method method) {
        // 获取方法上的 @Bean 注解。
        Bean bean = method.getAnnotation(Bean.class);
        String name = bean.value();
        if (name.isEmpty()) {
            // 如果 @Bean 注解的 value 属性为空，则使用方法名作为 Bean 名称。
            name = method.getName();
        }
        return name;
    }

    /**
     * 从类上获取 Bean 的名称。用于处理标注了 @Component 注解的类。
     * @param clazz 标注了 @Component 注解的类。
     * @return 类的 Bean 名称。如果 @Component 注解的 value 属性为空，则使用类名的首字母小写作为 Bean 名称。
     */
    public static String getBeanName(Class<?> clazz) {
        String name = "";
        // 查找@Component:
        Component component = clazz.getAnnotation(Component.class);
        if (component != null) {
            // @Component exist:
            name = component.value();
        } else {
            // 未找到@Component，继续在其他注解中查找@Component:
            for (Annotation anno : clazz.getAnnotations()) {
                if (findAnnotation(anno.annotationType(), Component.class) != null) {
                    try {
                        name = (String) anno.annotationType().getMethod("value").invoke(anno);
                    } catch (ReflectiveOperationException e) {
                        throw new BeanDefinitionException("Cannot get annotation value.", e);
                    }
                }
            }
        }
        if (name.isEmpty()) {
            // 如果未找到 @Component 注解，则使用类名的首字母小写作为 Bean 名称。
            name = clazz.getSimpleName();
            name = Character.toLowerCase(name.charAt(0)) + name.substring(1);
        }
        return name;
    }

    /**
     * 查找标注了指定注解的方法，方法不能有参数。
     * @param clazz 要查找的类。
     * @param annoClass 注解类型。
     * @return 如果找到一个标注了指定注解且没有参数的方法，则返回该方法。如果找到多个或没有方法，则抛出 BeanDefinitionException 异常。
     */
    @Nullable
    public static Method findAnnotationMethod(Class<?> clazz, Class<? extends Annotation> annoClass) {
        // try get declared method:
        List<Method> ms = Arrays.stream(clazz.getDeclaredMethods()).filter(m -> m.isAnnotationPresent(annoClass)).map(m -> {
            // 获取类中所有标注了指定注解的方法。
            if (m.getParameterCount() != 0) {
                // 检查这些方法是否有参数。如果有参数，抛出异常。
                throw new BeanDefinitionException(
                        String.format("Method '%s' with @%s must not have argument: %s", m.getName(), annoClass.getSimpleName(), clazz.getName()));
            }
            return m;
        }).collect(Collectors.toList());
        if (ms.isEmpty()) {
            return null;
        }
        if (ms.size() == 1) {
            return ms.get(0);
        }
        // 如果找到多个标注了指定注解的方法，抛出异常。
        throw new BeanDefinitionException(String.format("Multiple methods with @%s found in class: %s", annoClass.getSimpleName(), clazz.getName()));
    }

    /**
     * 根据方法名获取无参数的方法。
     * @param clazz 要查找的类。
     * @param methodName 方法名。
     * @return 返回指定名称的方法。如果找不到，则抛出 BeanDefinitionException 异常。
     */
    public static Method getNamedMethod(Class<?> clazz, String methodName) {
        try {
            return clazz.getDeclaredMethod(methodName);
        } catch (ReflectiveOperationException e) {
            throw new BeanDefinitionException(String.format("Method '%s' not found in class: %s", methodName, clazz.getName()));
        }
    }
}
